package com.wanfan.wanfanBrush.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.base.Predicates;
import com.wanfan.wanfanBrush.common.ErrorCode;
import com.wanfan.wanfanBrush.common.SingletonThreadPool;
import com.wanfan.wanfanBrush.exception.BusinessException;
import com.wanfan.wanfanBrush.mapper.QuestionBankQuestionMapper;
import com.wanfan.wanfanBrush.service.QuestionBankService;
import com.wanfan.wanfanBrush.service.QuestionService;
import com.wanfan.wanfanBrush.service.UserService;
import com.wanfan.wanfanBrush.utils.SqlUtils;
import com.wanfan.wanfanBrush.constant.CommonConstant;
import com.wanfan.wanfanBrush.exception.ThrowUtils;
import com.wanfan.wanfanBrush.model.dto.questionBankQuestion.QuestionBankQuestionQueryRequest;
import com.wanfan.wanfanBrush.model.entity.Question;
import com.wanfan.wanfanBrush.model.entity.QuestionBank;
import com.wanfan.wanfanBrush.model.entity.QuestionBankQuestion;
import com.wanfan.wanfanBrush.model.entity.User;
import com.wanfan.wanfanBrush.model.vo.QuestionBankQuestionVO;
import com.wanfan.wanfanBrush.model.vo.UserVO;
import com.wanfan.wanfanBrush.service.QuestionBankQuestionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 题库题目关联服务实现
 *
 * @author wanfan
 */
@Service
@Slf4j
public class QuestionBankQuestionServiceImpl extends ServiceImpl<QuestionBankQuestionMapper, QuestionBankQuestion> implements QuestionBankQuestionService {

    @Resource
    private UserService userService;

    @Resource
    @Lazy
    private QuestionService questionService;

    @Resource
    private QuestionBankService questionBankService;

    @Resource
    private QuestionBankQuestionMapper questionBankQuestionMapper;

    /**
     * 校验数据
     *
     * @param questionBankQuestion
     * @param add                  对创建的数据进行校验
     */
    @Override
    public void validQuestionBankQuestion(QuestionBankQuestion questionBankQuestion, boolean add) {
        ThrowUtils.throwIf(questionBankQuestion == null, ErrorCode.PARAMS_ERROR);
        // 题目和题库必须存在
        Long questionId = questionBankQuestion.getQuestionId();
        if (questionId != null) {
            Question question = questionService.getById(questionId);
            ThrowUtils.throwIf(question == null, ErrorCode.NOT_FOUND_ERROR, "题目不存在");
        }
        Long questionBankId = questionBankQuestion.getQuestionBankId();
        if (questionBankId != null) {
            QuestionBank questionBank = questionBankService.getById(questionBankId);
            ThrowUtils.throwIf(questionBank == null, ErrorCode.NOT_FOUND_ERROR, "题库不存在");
        }

        // 不需要校验
//        // todo 从对象中取值
//        String title = questionBankQuestion.getTitle();
//        // 创建数据时，参数不能为空
//        if (add) {
//            // todo 补充校验规则
//            ThrowUtils.throwIf(StringUtils.isBlank(title), ErrorCode.PARAMS_ERROR);
//        }
//        // 修改数据时，有参数则校验
//        // todo 补充校验规则
//        if (StringUtils.isNotBlank(title)) {
//            ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长");
//        }
    }

    /**
     * 获取查询条件
     *
     * @param questionBankQuestionQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<QuestionBankQuestion> getQueryWrapper(QuestionBankQuestionQueryRequest questionBankQuestionQueryRequest) {
        QueryWrapper<QuestionBankQuestion> queryWrapper = new QueryWrapper<>();
        if (questionBankQuestionQueryRequest == null) {
            return queryWrapper;
        }
        // todo 从对象中取值
        Long id = questionBankQuestionQueryRequest.getId();
        Long notId = questionBankQuestionQueryRequest.getNotId();
        String sortField = questionBankQuestionQueryRequest.getSortField();
        String sortOrder = questionBankQuestionQueryRequest.getSortOrder();
        Long questionBankId = questionBankQuestionQueryRequest.getQuestionBankId();
        Long questionId = questionBankQuestionQueryRequest.getQuestionId();
        Long userId = questionBankQuestionQueryRequest.getUserId();
        // todo 补充需要的查询条件
        // 精确查询
        queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionBankId), "questionBankId", questionBankId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionId), "questionId", questionId);
        // 排序规则
        queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取题库题目关联封装
     *
     * @param questionBankQuestion
     * @param request
     * @return
     */
    @Override
    public QuestionBankQuestionVO getQuestionBankQuestionVO(QuestionBankQuestion questionBankQuestion, HttpServletRequest request) {
        // 对象转封装类
        QuestionBankQuestionVO questionBankQuestionVO = QuestionBankQuestionVO.objToVo(questionBankQuestion);

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Long userId = questionBankQuestion.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        UserVO userVO = userService.getUserVO(user);
        questionBankQuestionVO.setUser(userVO);
        // endregion

        return questionBankQuestionVO;
    }

    /**
     * 分页获取题库题目关联封装
     *
     * @param questionBankQuestionPage
     * @param request
     * @return
     */
    @Override
    public Page<QuestionBankQuestionVO> getQuestionBankQuestionVOPage(Page<QuestionBankQuestion> questionBankQuestionPage, HttpServletRequest request) {
        List<QuestionBankQuestion> questionBankQuestionList = questionBankQuestionPage.getRecords();
        Page<QuestionBankQuestionVO> questionBankQuestionVOPage = new Page<>(questionBankQuestionPage.getCurrent(), questionBankQuestionPage.getSize(), questionBankQuestionPage.getTotal());
        if (CollUtil.isEmpty(questionBankQuestionList)) {
            return questionBankQuestionVOPage;
        }
        // 对象列表 => 封装对象列表
        List<QuestionBankQuestionVO> questionBankQuestionVOList = questionBankQuestionList.stream().map(questionBankQuestion -> {
            return QuestionBankQuestionVO.objToVo(questionBankQuestion);
        }).collect(Collectors.toList());

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Set<Long> userIdSet = questionBankQuestionList.stream().map(QuestionBankQuestion::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        // 填充信息
        questionBankQuestionVOList.forEach(questionBankQuestionVO -> {
            Long userId = questionBankQuestionVO.getUserId();
            User user = null;
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);
            }
            questionBankQuestionVO.setUser(userService.getUserVO(user));
        });
        // endregion

        questionBankQuestionVOPage.setRecords(questionBankQuestionVOList);
        return questionBankQuestionVOPage;
    }

    //批量增加题目到题库
    @Override
    public void batchAddQuestionsToBank(List<Long> questionIdList, long questionBankId, User loginUser) {
        //参数校验
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR, "题目列表不能为空");
        ThrowUtils.throwIf(questionBankId <= 0, ErrorCode.PARAMS_ERROR, "题库id不合法");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        //检查题目id是否全部存在 TODO(不使用 select * 查询)
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = Wrappers.lambdaQuery(Question.class)
                .select(Question::getId)
                .in(Question::getId, questionIdList);
        List<Question> questionList = questionService.list(questionLambdaQueryWrapper);
        //筛选出合法的题目id
        List<Long> vaildQuestionIdList = questionList.stream().map(Question::getId).collect(Collectors.toList());
        //检查哪些题目id还不存在于题库中，避免重复插入
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                .in(QuestionBankQuestion::getQuestionId, vaildQuestionIdList);
        //查询出已存在题库中的题目列表
        List<QuestionBankQuestion> existQuestionList = this.list(lambdaQueryWrapper);
        //存在于题库中的id列表
        Set<Long> existQuestionIdSet = existQuestionList.stream().map(QuestionBankQuestion::getQuestionId).collect(Collectors.toSet());
        //从所有的题目id中过滤出不存在题库中的题目id TODO：过滤出来的就是需要向题库中插入的列表
        vaildQuestionIdList.stream().filter(questionId -> !existQuestionIdSet.contains(questionId)).collect(Collectors.toList());
        ThrowUtils.throwIf(CollUtil.isEmpty(vaildQuestionIdList), ErrorCode.PARAMS_ERROR, "所有题目都存在于题库中");
        //检查题库id是否存在
        QuestionBank questionBank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(questionBank == null, ErrorCode.NOT_FOUND_ERROR, "题库不存在");

//        //自定义线程池
//        ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
//                4                   //核心线程数 服务器是2核2G轻量级，使用CPU核心数的两倍作为核心线程
//                , 10                //最大线程数，选用10条线程做最大线程数
//                , 60L               //线程空闲存活时间 当线程池中使用的线程超过了核心线程数后60s没有任何任务自动销毁
//                , TimeUnit.SECONDS  //存活时间单位
//                , new LinkedBlockingDeque<>(1000)   //阻塞队列容量
//                , new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略，由调用线程处理任务
//        );
        ThreadPoolExecutor customExecutor = SingletonThreadPool.getInstance();
        // 用于保存所有批次的CompletableFuture
        List<CompletableFuture<Void>> futures = new ArrayList<>();

        //TODO： 分批处理长事务 假设每次处理1000条
        int batchSize = 1000;
        int totalQuestionListSize = vaildQuestionIdList.size();
        for (int i = 0; i < totalQuestionListSize; i += batchSize) {
            //生成每批次的数据
            List<Long> subList = vaildQuestionIdList.subList(i, Math.min(i + batchSize, totalQuestionListSize));
            List<QuestionBankQuestion> questionBankQuestionList = subList.stream()
                    .map(questionId -> {
                        QuestionBankQuestion questionBankQuestion = new QuestionBankQuestion();
                        questionBankQuestion.setQuestionId(questionId);
                        questionBankQuestion.setQuestionBankId(questionBankId);
                        questionBankQuestion.setUserId(loginUser.getId());
                        return questionBankQuestion;
                    })
                    .collect(Collectors.toList());
            //TODO: 执行事务前需通过AOP获取代理 如果直接调用方法不会触发事务
            QuestionBankQuestionService questionBankQuestionService = (QuestionBankQuestionService) AopContext.currentProxy();
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                questionBankQuestionService.batchAddQuestionsToBankInner(questionBankQuestionList);
            }, customExecutor).exceptionally(ex -> {
                log.error("批处理任务执行失败", ex);
                return null;
            });
            futures.add(future);
        }

        //阻塞等待任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        //任务完成后关闭线程池
        customExecutor.shutdown();
    }
//TODO：使用循环进行插入数据
    //    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public void batchAddQuestionsToBankInner(List<QuestionBankQuestion> questionBankQuestions) {
//        // TODO: 定义重试器
//        Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
//                .retryIfResult(Predicates.<Boolean>isNull()) // 如果结果为空则重试
//                .retryIfExceptionOfType(IOException.class) // 发生IO异常则重试
//                .retryIfRuntimeException() // 发生运行时异常则重试
//                .withWaitStrategy(WaitStrategies.incrementingWait(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS)) // 等待
//                .withStopStrategy(StopStrategies.stopAfterAttempt(4)) // 允许执行4次（首次执行 + 最多重试3次）
//                .build();
//        for (QuestionBankQuestion questionBankQuestion : questionBankQuestions) {
//            long questionId = questionBankQuestion.getQuestionId();
//            long questionBankId = questionBankQuestion.getQuestionBankId();
//            try {
////                boolean result = this.save(questionBankQuestion);
//                //TODO： 使用重试器为题库添加题目进行重试
//                Callable<Boolean> callable = () -> {
//                    return this.save(questionBankQuestion); // 业务逻辑
//                };
//                Boolean result = retryer.call(callable);
//                if (!result) {
//                    throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
//                }
//            } catch (DataIntegrityViolationException e) {
//                log.error("数据库唯一键冲突或违反其他完整性约束，题目 id: {}, 题库 id: {}, 错误信息: {}",
//                        questionId, questionBankId, e.getMessage());
//                throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目已存在于该题库，无法重复添加");
//            } catch (DataAccessException e) {
//                log.error("数据库连接问题、事务问题等导致操作失败，题目 id: {}, 题库 id: {}, 错误信息: {}",
//                        questionId, questionBankId, e.getMessage());
//                throw new BusinessException(ErrorCode.OPERATION_ERROR, "数据库操作失败");
//            } catch (Exception e) {
//                // 捕获其他异常，做通用处理
//                log.error("添加题目到题库时发生未知错误，题目 id: {}, 题库 id: {}, 错误信息: {}",
//                        questionId, questionBankId, e.getMessage());
//                throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
//            }
//        }
//
//    }

    /**
     * 使用事务控制 分批处理
     * 使用savaBatch插入多条数据
     * 如果出现网络问题或其他问题使用重试器实现重试机制
     *
     * @param questionBankQuestions
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAddQuestionsToBankInner(List<QuestionBankQuestion> questionBankQuestions) {
        // TODO: 定义重试器
        Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                .retryIfResult(Predicates.<Boolean>isNull()) // 如果结果为空则重试
                .retryIfExceptionOfType(IOException.class) // 发生IO异常则重试
                .retryIfRuntimeException() // 发生运行时异常则重试
                .withWaitStrategy(WaitStrategies.incrementingWait(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS)) // 等待
                .withStopStrategy(StopStrategies.stopAfterAttempt(4)) // 允许执行4次（首次执行 + 最多重试3次）
                .build();
        try {
            //TODO： 使用重试器为题库添加题目进行重试
            Callable<Boolean> callable = () -> {
                return this.saveBatch(questionBankQuestions); // 业务逻辑
            };
            boolean result = retryer.call(callable);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
        } catch (DataIntegrityViolationException e) {
            log.error("数据库唯一键冲突或违反其他完整性约束, 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目已存在于该题库，无法重复添加");
        } catch (DataAccessException e) {
            log.error("数据库连接问题、事务问题等导致操作失败, 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "数据库操作失败");
        } catch (Exception e) {
            // 捕获其他异常，做通用处理
            log.error("添加题目到题库时发生未知错误错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
        }
    }

    /**
     * 从题库中移除题目
     *
     * @param questionIdList
     * @param questionBankId
     */
    @Override
    public void batchRemoveQuestionsFromBank(List<Long> questionIdList, long questionBankId,User loginUser) {
        //参数校验
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR, "题目列表不能为空");
        ThrowUtils.throwIf(questionBankId <= 0, ErrorCode.PARAMS_ERROR, "题库id不合法");

        //将for循环的循环删除改为批量删除
        //校验是否存在题库id
        QuestionBank questionBank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(questionBank == null, ErrorCode.NOT_FOUND_ERROR, "题库不存在");
        //找出所有在题库中的题目
        //1.通过题目id去提目表中查询题目
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = Wrappers.lambdaQuery(Question.class)
                .select(Question::getId)
                .in(Question::getId, questionIdList);
        //所有题目
        List<Question> questionList = questionService.list(questionLambdaQueryWrapper);
        //所有合法的题目id
        List<Long> vaildQuestionIdList = questionList.stream().map(Question::getId).collect(Collectors.toList());
        //找出所有存在题库表中的题目
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                .in(QuestionBankQuestion::getQuestionId, vaildQuestionIdList);
        //所有存在题库中的题目id
        List<QuestionBankQuestion> existQuestionList = this.list(lambdaQueryWrapper);
        vaildQuestionIdList = existQuestionList.stream()
                .map(QuestionBankQuestion::getQuestionId)
                .collect(Collectors.toList());
        ThrowUtils.throwIf(CollUtil.isEmpty(vaildQuestionIdList), ErrorCode.PARAMS_ERROR, "所有题目都不存在题库中");
        //将题目id批量删除 使用线程池批量处理
        ThreadPoolExecutor threadPoolExecutor = SingletonThreadPool.getInstance();
        // 用于保存所有批次的CompletableFuture
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        //分批处理
        int batchSize = 1000;
        int totalQuestionListSize = vaildQuestionIdList.size();
        for (int i = 0; i < totalQuestionListSize; i+=batchSize) {
            List<Long> subList = vaildQuestionIdList.subList(i, Math.min(i + batchSize, totalQuestionListSize));
            //TODO: 执行事务前需通过AOP获取代理 如果直接调用方法不会触发事务
            QuestionBankQuestionService questionBankQuestionService = (QuestionBankQuestionService) AopContext.currentProxy();
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                questionBankQuestionService.batchRemoveQuestionsToBankInner(subList,questionBankId);
            }, threadPoolExecutor).exceptionally(ex -> {
                log.error("批处理任务执行失败", ex);
                return null;
            });
            futures.add(future);
        }
    }

    /**
     * 使用事务控制 分批处理
     * 使用removeByIds批量删除数据
     * 如果出现网络问题或其他问题使用重试器实现重试机制
     *
     * @param questionIds
     * @param questionBankId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchRemoveQuestionsToBankInner(List<Long> questionIds, long questionBankId) {
        // TODO: 定义重试器
        Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                .retryIfResult(Predicates.<Boolean>isNull()) // 如果结果为空则重试
                .retryIfExceptionOfType(IOException.class) // 发生IO异常则重试
                .retryIfRuntimeException() // 发生运行时异常则重试
                .withWaitStrategy(WaitStrategies.incrementingWait(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS)) // 等待
                .withStopStrategy(StopStrategies.stopAfterAttempt(4)) // 允许执行4次（首次执行 + 最多重试3次）
                .build();
        try {
            //TODO： 使用重试器为题库添加题目进行重试
            Callable<Boolean> callable = () -> questionBankQuestionMapper.removeBatch(questionIds,questionBankId);
            boolean result = retryer.call(callable);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
        } catch (DataIntegrityViolationException e) {
            log.error("数据库唯一键冲突或违反其他完整性约束, 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目已存在于该题库，无法重复添加");
        } catch (DataAccessException e) {
            log.error("数据库连接问题、事务问题等导致操作失败, 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "数据库操作失败");
        } catch (Exception e) {
            // 捕获其他异常，做通用处理
            log.error("添加题目到题库时发生未知错误错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
        }
    }
}
